/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package guzik.lukasz.snpexplorer;

import SnpDbEntities.Category;
import SnpDbEntities.CodeTypes;
import SnpDbEntities.Snippet;
import guzik.lukasz.SnpDataAPI.SnpDataAPI;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;

/**
 *
 * @author Łukasz 'byku' Guzik
 */
public class TreeDropTarget implements DropTargetListener
{
    private DropTarget target;
    private JTree targetTree;
    private TreePath previousSelection;
    private boolean isPublicTree;

    public TreeDropTarget(JTree targetTree, boolean isPublicTree)
    {
	this.targetTree = targetTree;
	target = new DropTarget(targetTree, this);
	this.isPublicTree = isPublicTree;
    }

    private DefaultMutableTreeNode getNodeForEvent(DropTargetDragEvent dtde)
    {
	Point p = dtde.getLocation();
	TreePath path = targetTree.getPathForLocation(p.x, p.y);
	if(path == null)
	{
	    return null;
	}
	return (DefaultMutableTreeNode) path.getLastPathComponent();
    }

    @Override
    public void dragEnter(DropTargetDragEvent dtde)
    {
	previousSelection = targetTree.getSelectionPath();
	onDragEnterOver(dtde);
    }

    @Override
    public void dragOver(DropTargetDragEvent dtde)
    {
	onDragEnterOver(dtde);
    }

    @Override
    public void dropActionChanged(DropTargetDragEvent dtde)
    {
    }

    @Override
    public void dragExit(DropTargetEvent dte)
    {
	targetTree.setSelectionPath(previousSelection);
    }

    @Override
    public void drop(DropTargetDropEvent dtde)
    {
	Point targetLoc = dtde.getLocation();
	TreePath targetPath = targetTree.getPathForLocation(targetLoc.x, targetLoc.y);
	DefaultMutableTreeNode targetNode;
	boolean targetIsRoot = false;

	if(targetPath != null)
	{
	    targetNode = (DefaultMutableTreeNode) targetPath.getLastPathComponent();
	} else
	{
	    targetNode = (DefaultMutableTreeNode) ((DefaultTreeModel) targetTree.getModel()).getRoot();
	    targetPath = new TreePath(targetNode.getPath());
	    targetIsRoot = true;
	}

	if(targetNode.getUserObject() instanceof Snippet)
	{
	    dtde.rejectDrop();
	    return;
	}

	try
	{
	    Transferable tr = dtde.getTransferable();
	    DataFlavor[] flavors = tr.getTransferDataFlavors();
	    for(DataFlavor fl : flavors)
	    {
		if(tr.isDataFlavorSupported(TransferableTreeNode.TREE_PATH_FLAVOR))
		{
		    TreePath sourcePath = (TreePath) tr.getTransferData(TransferableTreeNode.TREE_PATH_FLAVOR);

		    if(movingToChildItselfParent(targetPath, sourcePath))
		    {
			dtde.rejectDrop();
			return;
		    }

		    dtde.acceptDrop(DnDConstants.ACTION_MOVE);

		    DefaultMutableTreeNode sourceNode =
			    (DefaultMutableTreeNode) sourcePath.getLastPathComponent();

		    if(targetIsRoot)
		    {
			updateCategoryOrSnippet(sourceNode.getUserObject(), null);
		    } else
		    {
			updateCategoryOrSnippet(sourceNode.getUserObject(),
				(Category) targetNode.getUserObject());
		    }

		    DefaultTreeModel model = (DefaultTreeModel) targetTree.getModel();
		    model.insertNodeInto(sourceNode, targetNode, targetNode.getChildCount());

		    sourcePath = new TreePath(((DefaultMutableTreeNode) targetNode.getLastChild()).getPath());
		    targetTree.setSelectionPath(sourcePath);
		    model.nodeChanged(sourceNode);
		    dtde.dropComplete(true);
		    return;
		}

		if(!isPublicTree && tr.isDataFlavorSupported(DataFlavor.javaFileListFlavor))
		{
		    dtde.acceptDrop(dtde.getDropAction());
		    List<File> fileList = (List<File>) tr.getTransferData(fl);
		    for(File file : fileList)
		    {
			if(file.isFile())
			{
			    String name = file.getName();
			    String ext = name.substring(name.lastIndexOf('.') + 1);
			    name = name.substring(0, name.lastIndexOf('.'));
			    String data = readFile(file);

			    Snippet newSnp = new Snippet();
			    newSnp.setName(name);
			    newSnp.setDesc(null);
			    newSnp.setLastModified(new Date());
			    newSnp.setCategoryId(((Category) targetNode.getUserObject()).getId());
			    newSnp.setOwnerId(SnpDataAPI.getDefault().getConnectedUser().getId());
			    newSnp.setData(data);
			    newSnp.setCodeType(codeTypeByExt(ext));

			    int newId = SnpDataAPI.getDefault().addPrivateSnippet(newSnp);
			    if(newId == -1)
			    {
				NotifyDescriptor nd = new NotifyDescriptor.Message("Nie udało się dodać " + file.getName(), NotifyDescriptor.ERROR_MESSAGE);
				DialogDisplayer.getDefault().notify(nd);
			    } else
			    {
				newSnp.setId(newId);
				SnpDataAPI.getDefault().getAllPrivateSnippets().put(newId, newSnp);
				((DefaultTreeModel) targetTree.getModel()).insertNodeInto(new DefaultMutableTreeNode(newSnp), targetNode, 0);
			    }
			}
		    }
		    dtde.dropComplete(true);
		}
	    }
	    dtde.rejectDrop();
	} catch(Exception e)
	{
	    dtde.rejectDrop();
	}
    }

    private void onDragEnterOver(DropTargetDragEvent dtde)
    {
	DefaultMutableTreeNode node = getNodeForEvent(dtde);

	if(node == null)
	{
	    try
	    {
		Transferable tr = dtde.getTransferable();
		DataFlavor[] flavors = tr.getTransferDataFlavors();
		for(DataFlavor fl : flavors)
		{
		    if(tr.isDataFlavorSupported(fl)
			    && fl == TransferableTreeNode.TREE_PATH_FLAVOR)
		    {
			TreePath sourcePath = (TreePath) tr.getTransferData(fl);
			node = (DefaultMutableTreeNode) sourcePath.getLastPathComponent();
			if(node.getUserObject() instanceof Category)
			{
			    targetTree.clearSelection();
			    dtde.acceptDrag(dtde.getDropAction());
			    return;
			} else
			{
			    targetTree.setSelectionPath(previousSelection);
			    dtde.rejectDrag();
			    return;
			}
		    }
		}
		targetTree.setSelectionPath(previousSelection);
		dtde.rejectDrag();
	    } catch(Exception e)
	    {
		targetTree.setSelectionPath(previousSelection);
		dtde.rejectDrag();
	    }
	} else if(node.getUserObject() instanceof Snippet)
	{
	    targetTree.setSelectionPath(previousSelection);
	    dtde.rejectDrag();
	} else
	{
	    targetTree.setSelectionPath(new TreePath(node.getPath()));
	    dtde.acceptDrag(dtde.getDropAction());
	}
    }

    private boolean movingToChildItselfParent(TreePath targetPath, TreePath sourcePath)
    {
	Object sourceUserObject = ((DefaultMutableTreeNode) sourcePath.getLastPathComponent()).getUserObject();
	Object targetUserObject = ((DefaultMutableTreeNode) targetPath.getLastPathComponent()).getUserObject();
	Object userObjectInTarget;
	int sourceCategoryID;

	if(sourceUserObject instanceof Snippet)
	{
	    if(((Snippet) sourceUserObject).getCategoryId() == ((Category) targetUserObject).getId())
	    {
		return true;
	    } else
	    {
		return false;
	    }
	}

	if(targetUserObject instanceof Category)
	{
	    if(((Category) sourceUserObject).getParentId() == ((Category) targetUserObject).getId())
	    {
		return true;
	    }

	    sourceCategoryID = ((Category) sourceUserObject).getId();

	    for(int i = targetPath.getPathCount() - 1; i > 0; i--)
	    {
		userObjectInTarget =
			((DefaultMutableTreeNode) targetPath.getPathComponent(i)).getUserObject();
		if((userObjectInTarget instanceof Category)
			&& ((Category) userObjectInTarget).getId() == sourceCategoryID)
		{
		    return true;
		}
	    }
	} else if((targetUserObject instanceof String)
		&& ((Category) sourceUserObject).getParentId() == 0)
	{
	    return true;
	}

	return false;
    }

    private void updateCategoryOrSnippet(Object source, Category target)
    {
	if(source instanceof Category)
	{
	    Category catToUpdate = (Category) source;
	    if(target == null)
	    {
		catToUpdate.setParentId(0);
	    } else
	    {
		catToUpdate.setParentId(target.getId());
	    }
	    SnpDataAPI.getDefault().updateCategory(catToUpdate, isPublicTree);
	} else if(source instanceof Snippet)
	{
	    Snippet snpToUpdate = (Snippet) source;
	    snpToUpdate.setCategoryId(target.getId());
	    SnpDataAPI.getDefault().updateSnippet(snpToUpdate, false, isPublicTree);
	}
    }

    private String readFile(File file) throws FileNotFoundException, IOException
    {
	BufferedReader br = new BufferedReader(new FileReader(file));
	StringBuilder sb = new StringBuilder();

	while(br.ready())
	{
	    sb.append(br.readLine());
	    sb.append(System.getProperty("line.separator"));
	}
	br.close();
	return sb.toString().trim();
    }

    private CodeTypes codeTypeByExt(String ext)
    {
	CodeTypes retVal;
	if(ext.equalsIgnoreCase("c"))
	{
	    retVal = CodeTypes.C;
	} else if(ext.equalsIgnoreCase("cpp") || ext.equalsIgnoreCase("h")
		|| ext.equalsIgnoreCase("hpp"))
	{
	    retVal = CodeTypes.CPP;
	} else if(ext.equalsIgnoreCase("html") || ext.equalsIgnoreCase("htm"))
	{
	    retVal = CodeTypes.HTML;
	} else if(ext.equalsIgnoreCase("java"))
	{
	    retVal = CodeTypes.JAVA;
	} else if(ext.equalsIgnoreCase("js"))
	{
	    retVal = CodeTypes.JAVA_SCRIPT;
	} else if(ext.equalsIgnoreCase("latex"))
	{
	    retVal = CodeTypes.LATEX;
	} else if(ext.equalsIgnoreCase("properties") || ext.equalsIgnoreCase("ini"))
	{
	    retVal = CodeTypes.PROPERTIES;
	} else if(ext.equalsIgnoreCase("sql"))
	{
	    retVal = CodeTypes.SQL;
	} else
	{
	    retVal = CodeTypes.PLAIN;
	}

	return retVal;
    }

    public void setEnabled(boolean enabled)
    {
	target.setActive(enabled);
    }
}
